package org.dreamwork.tools;

import javafx.application.Application;
import org.dreamwork.tools.concurrent.SimpleBroadcaster;
import org.dreamwork.tools.io.UpdateListener;
import org.dreamwork.tools.io.UpdateStub;
import org.dreamwork.tools.jfx.FXSplash;

import javax.swing.*;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Created by seth.yang on 2018/12/6
 */
public class ApplicationShell {
    private static Process process;
    private static final Logger logger = LogFactory.getLogger (ApplicationShell.class);

    private static Properties props;
    private static String[]   args;
    private static boolean    killed;

    public static void main (String[] args) throws Exception {
        UIManager.setLookAndFeel (UIManager.getSystemLookAndFeelClassName ());
        boolean splash_screen = false;
        if (args.length > 0) {
            for (String p : args) {
                if ("--enable-splash".equals (p)) {
                    splash_screen = true;
                    break;
                }
            }
        }

        if (splash_screen) {
            LocalUtil.service.execute (()-> Application.launch (FXSplash.class));
        }

        if (logger.isLoggable (Level.FINE)) {
            logger.fine ("trying to parse config file");
        }
        File file = new File ("../conf/application-shell.conf");
        if (!file.exists ()) {
            logger.log (Level.SEVERE, "no config found. exit!");
            System.exit (-999);
            return;
        }

        Properties props = new Properties ();
        try (InputStream in = new FileInputStream (file)) {
            props.load (in);
        }

        if (!props.containsKey ("target.main-class")) {
            logger.log (Level.SEVERE, "no main class spec.");
            return;
        }

        if (logger.isLoggable (Level.FINE)) {
            logger.fine ("starting up the simple broadcaster");
        }
        // startup the simple broadcaster
        final SimpleBroadcaster broadcaster = SimpleBroadcaster.getInstance ();
        if (logger.isLoggable (Level.FINE)) {
            logger.fine ("starting the update listener");
        }
        final UpdateListener listener = UpdateListener.getInstance ();
        listener.bind ();

        Runtime.getRuntime ().addShutdownHook (new Thread (()->{
            // shutdown the broadcaster
            broadcaster.shutdown ();
            listener.shutdown ();
            UpdateManager.cancel ();
        }));

        ApplicationShell.props = props;
        ApplicationShell.args  = args;

        LocalUtil.service.execute (()-> {
            try {
                launch ();
            } catch (Exception ex) {
                logger.log (Level.SEVERE, ex, String::new);
            }
        });
    }

    public static void launch () throws IOException, InterruptedException {
        List<String> cmd = new ArrayList<> ();
        String javaHome = System.getProperty ("java.home");
        String execute  = javaHome + File.separatorChar + "bin" + File.separatorChar + "java";
        cmd.add (execute);

        cmd.add ("-D" + UpdateStub.KEY_STUB_PORT + "=" + UpdateListener.getInstance ().getPort ());
        cmd.add ("-Dwarp-mode");
        if (props.containsKey ("target.vm.args")) {
            String s = props.getProperty ("target.vm.args");
            cmd.addAll (Arrays.asList (s.split ("[\\s]")));
        }
        if (props.containsKey ("target.lib")) {
            String lib = props.getProperty ("target.lib");
            File dir = new File (lib);
            File[] jars = dir.listFiles (file-> file.getName ().endsWith (".jar"));
            if (jars != null && jars.length > 0) {
                StringBuilder builder = new StringBuilder ();
                String pathSeparator  = System.getProperty ("path.separator");
                String fileSeparator  = System.getProperty ("file.separator");
                for (File jar : jars) {
                    if (builder.length () > 0) {
                        builder.append (pathSeparator);
                    }
                    builder.append (lib).append (fileSeparator).append (jar.getName ());
                }

                cmd.add ("-cp");
                cmd.add (builder.toString ());
            }
        }

        cmd.add (props.getProperty ("target.main-class"));

        if (args.length > 0)
            cmd.addAll (Arrays.asList (args));
        if (props.containsKey ("target.app.args")) {
            cmd.addAll (Arrays.asList (props.getProperty ("target.app.args").split ("[\\s]")));
        }

        File stdout = null, stderr = null;
        if (props.containsKey ("target.redirect")) {
            String redirect = props.getProperty ("target.redirect");
            if ("true".equals (redirect)) {
                String s_stdout = props.getProperty ("target.stdout");
                String s_stderr = props.getProperty ("target.stderr");

                stderr = new File (s_stderr);
                File parent = stderr.getParentFile ();
                if (!parent.exists () && !parent.mkdirs ()) {
                    System.out.println ("Can't create stderr");
                    stderr = null;
                }

                stdout = new File (s_stdout);
                parent = stdout.getParentFile ();
                if (!parent.exists () && !parent.mkdirs ()) {
                    System.out.println ("Can't create stdout");
                    stdout = null;
                }
            }
        }

        process = new ProcessBuilder (cmd).start ();
        killed  = false;
        if (stdout != null) {
            final File f_out = stdout;
            LocalUtil.service.execute (()->{
                try (InputStream in = process.getInputStream ()) {
                    try (OutputStream out = new FileOutputStream (f_out)) {
                        redirect (in, out);
                    }
                } catch (Exception ex) {
                    logger.log (Level.SEVERE, ex, String::new);
                }
            });
        }
        if (stderr != null) {
            final File f_out = stderr;
            LocalUtil.service.execute (()->{
                try (InputStream in = process.getErrorStream ()) {
                    try (OutputStream out = new FileOutputStream (f_out)) {
                        redirect (in, out);
                    }
                } catch (Exception ex) {
                    logger.log (Level.SEVERE, ex, String::new);
                }
            });
        }
        int code = process.waitFor ();
        if (!killed)
            System.exit (code);
    }

    static void kill () {
        killed = true;
        if (process != null) {
            process.destroyForcibly ();
        }
    }

    private static void redirect (InputStream in, OutputStream out) throws IOException {
        byte[] buff = new byte[1<<13];  // 8k
        int length;
        while ((length = in.read (buff)) != -1) {
            out.write (buff, 0, length);
            out.flush ();
        }
    }
}